/*
 * Miccsnookersim, a snooker simulation with AI
 * Copyright (C) 2009 Enno Ruijters, Frans van den Heuvel, Yannick Thimister,
 * Ali B. Ozmen, Esther Verhoef, Achim Leydecker
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.

 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */


import java.awt.*;
import java.awt.event.MouseListener;
import javax.swing.*;
import java.awt.geom.*;

/**
 * Class to display the snooker table on screen
 */
public class Display
{
	public static final double PIXELS_PER_CM = 2;
	public static final double AIMSIZE = 12; // the amount the ball gets enlarged for the effect-ball
	private static final int CUE_CIRCLE_RADIUS = 3;
	private JFrame frame;
	private SnookerTable table;
	private Table t;
	private Label[] scoreFields;

	public static double bigBallWidth;
	public static double[] xAndYSpin;

        public static final int yOffset = 200;
        public static final int xOffset = 200;
        
	class SnookerTable extends JComponent
	{
		private Table t;
		private Cue cue;
		private static final long serialVersionUID = 0;

		public SnookerTable(Table t)
		{
			this.t = t;
		}

		public void setCue(Cue newCue)
		{
			if (cue != null) {
				removeMouseListener(cue);
				removeMouseMotionListener(cue);
			}
			cue = newCue;
			if (cue != null) {
				addMouseListener(cue);
				addMouseMotionListener(cue);
			}
		}

		private void paintBlackLine(Graphics2D g2, Ball cueBall)
		{
			double[] positionCoords = null;
			double[] forceCoords = null;
			Vector tmp, oldPosition;
			Vector position = cue.getPosition();
			if (position != null) {
				oldPosition = position.clone();
			} else {
				tmp = cue.getForce().clone();
				tmp.scaleTo_(cueBall.getRadius());
				position = tmp.add_(cueBall.getPosition());
				cue.setPosition(position);
				oldPosition = null;
			}
			Vector direction = cue.getForce().clone().scale_(-1);
			direction.scaleTo_(200);
			positionCoords = cueBall.getPosition().clone()
			                        .scale_(PIXELS_PER_CM).getElements();
			forceCoords = direction.add_(cueBall.getPosition())
			                       .scale_(PIXELS_PER_CM).getElements();
			Line2D.Double line = new Line2D.Double(positionCoords[0]+xOffset,
			                                       positionCoords[1]+ yOffset,
			                                       forceCoords[0] + xOffset,
			                                       forceCoords[1] + yOffset);
			g2.setColor(Color.BLACK);
			g2.draw(line);
			cue.setPosition(oldPosition);
		}

		private void paintCue(Graphics2D g2)
		{
			double lineYPlace = cue.spinYPoint + yOffset;
			double lineXPlace = cue.spinXPoint + xOffset;
			if (lineYPlace == yOffset)
			{
				lineYPlace = xAndYSpin[1] + (bigBallWidth / 2);
			}
			if(lineXPlace == xOffset)
			{
				lineXPlace = xAndYSpin[0] + (bigBallWidth / 2); 
			}

			Rectangle2D.Double yBar = new Rectangle.Double(
					xAndYSpin[0] + (bigBallWidth / 2) - 5,
					lineYPlace, 10, 2);
			Rectangle2D.Double xBar = new Rectangle.Double(
					lineXPlace,
					xAndYSpin[1]+ (bigBallWidth / 2) - 5,
					2, 10);
			g2.setColor(Color.BLACK);
			g2.fill(yBar);
			g2.fill(xBar);

			Ball[] balls = t.getBalls();
			int i;
			Ball cueBall = balls[0];
			for (i = 0; i < balls.length; i++)
				if (balls[i].getColor() == Color.WHITE)
					cueBall = balls[i];
			if (cueBall.getPosition().magnitude() == 1.0/0.0)
				return;
			double[] positionCoords = null;
			double[] forceCoords = null;
			if (cue.getForce() == null)
				return;
			if (t.areBallsMoving())
				return;
			if (cue.getPosition() == null)
				return;
			positionCoords = cue.getPosition().getElements();
			positionCoords = (double[]) positionCoords.clone();
			forceCoords = Vector.add(cue.getPosition(),
			                         cue.getForce()).getElements();

			positionCoords[0] = positionCoords[0] * Display.PIXELS_PER_CM + xOffset;
			positionCoords[1] = positionCoords[1] * Display.PIXELS_PER_CM + yOffset;
			forceCoords[0] = forceCoords[0] * Display.PIXELS_PER_CM + xOffset;
			forceCoords[1] = forceCoords[1] * Display.PIXELS_PER_CM + yOffset;
			Ellipse2D.Double pointOfContact = new Ellipse2D.Double(
				    positionCoords[0] - CUE_CIRCLE_RADIUS,
				    positionCoords[1] - CUE_CIRCLE_RADIUS,
				    CUE_CIRCLE_RADIUS * 2,
				    CUE_CIRCLE_RADIUS * 2);
			g2.setColor(Color.RED);
			g2.fill(pointOfContact);
			Line2D.Double line = new Line2D.Double(positionCoords[0],
			                                       positionCoords[1],
			                                       forceCoords[0],
			                                       forceCoords[1]);
			g2.setColor(Color.WHITE);
			g2.draw(line);
			paintBlackLine(g2, cueBall);
		}

		public void paintComponent(Graphics g)
		{
			Graphics2D g2 = (Graphics2D) g;
			Rectangle2D tableRectangle =
			    new Rectangle2D.Double(xOffset, yOffset,
			                           Table.getTableWidth() * PIXELS_PER_CM,
			                           Table.getTableHeight() * PIXELS_PER_CM);
			g2.setColor(new Color(34, 139, 34));
			g2.fill(tableRectangle);

			/* Create an extra Ball next to the table where the topspin can
			 * be set */
			bigBallWidth = (2 * Ball.RADIUS * PIXELS_PER_CM) * AIMSIZE;
			xAndYSpin = new double[2];
			xAndYSpin[0] = tableRectangle.getWidth() + 20 + xOffset; //somewhere near the table but not right next to it
			xAndYSpin[1] = 10 + yOffset;
			Ellipse2D.Double ballSpin = new Ellipse2D.Double(xAndYSpin[0],
			                                                 xAndYSpin[1],
			                                                 bigBallWidth,
			                                                 bigBallWidth);
			Rectangle2D.Double ballFrame = new Rectangle2D.Double(xAndYSpin[0],
			                                                      xAndYSpin[1],
			                                                      bigBallWidth,
			                                                      bigBallWidth);
			Line2D.Double ballYLine = new Line2D.Double(xAndYSpin[0] + (bigBallWidth / 2), xAndYSpin[1], xAndYSpin[0] + (bigBallWidth / 2), bigBallWidth + xAndYSpin[1]);
			Line2D.Double ballXLine = new Line2D.Double(xAndYSpin[0], xAndYSpin[1] + (bigBallWidth / 2), xAndYSpin[0] + bigBallWidth, xAndYSpin[1] + (bigBallWidth / 2));
			g2.draw(ballSpin);
			g2.draw(ballYLine);
			g2.draw(ballXLine);
			g2.draw(ballFrame);
			if (cue != null) {
							}

			int radius = Table.getTableWidth()/12;
			Ellipse2D.Double circle = new Ellipse2D.Double(
			    (Table.getTableWidth()*0.75-radius) * PIXELS_PER_CM + xOffset,
			    (Table.getTableHeight()/2-radius) * PIXELS_PER_CM + yOffset,
			    2 * radius * PIXELS_PER_CM,
			    2 * radius * PIXELS_PER_CM);
			g2.setColor(Color.WHITE);
			g2.draw(circle);

			Rectangle2D tablePart = new Rectangle2D.Double(
			    (Table.getTableWidth() / 4 * 3 - radius) * PIXELS_PER_CM + xOffset,
			    (Table.getTableHeight() / 2 - radius) * PIXELS_PER_CM + yOffset,
			    radius * PIXELS_PER_CM,
			    (radius * 2 + 30) * PIXELS_PER_CM);
			g2.setColor(new Color(34, 139, 34));
			g2.fill(tablePart);

			Rectangle[] edges;
			g2.setColor(new Color(139, 69, 19));
			edges = Table.createEdges();
			for (Rectangle rectangle : edges) {
				rectangle.x  = rectangle.x * (int)PIXELS_PER_CM + xOffset;
				rectangle.y = rectangle.y * (int)PIXELS_PER_CM + yOffset;
				rectangle.height *= PIXELS_PER_CM;
				rectangle.width *= PIXELS_PER_CM;
				g2.fill(rectangle);
				rectangle.x = (rectangle.x - xOffset) / (int)PIXELS_PER_CM;
				rectangle.y = (rectangle.y - yOffset) / (int)PIXELS_PER_CM;
				rectangle.height /= PIXELS_PER_CM;
				rectangle.width /= PIXELS_PER_CM;
			}

			Rectangle[] corners;
			corners = Table.createCorner();
			g2.setColor(new Color(139, 69, 19));

			for (Rectangle rectangle : corners) {
				rectangle.x  = rectangle.x * (int)PIXELS_PER_CM + xOffset;
				rectangle.y = rectangle.y * (int)PIXELS_PER_CM + yOffset;
				rectangle.height *= PIXELS_PER_CM;
				rectangle.width *= PIXELS_PER_CM;
				g2.fill(rectangle);
				rectangle.x = (rectangle.x - xOffset) / (int)PIXELS_PER_CM;
				rectangle.y = (rectangle.y - yOffset) / (int)PIXELS_PER_CM;
				rectangle.height /= PIXELS_PER_CM;
				rectangle.width /= PIXELS_PER_CM;
			}

			Ellipse2D.Double[] pockets;
			pockets = Table.getPockets();
			g2.setColor(Color.BLACK);
			for (Ellipse2D.Double pocket : pockets) {
				Rectangle2D bounds = pocket.getFrame();
				pocket.setFrame(pocket.getX() * PIXELS_PER_CM + xOffset,
				                pocket.getY() * PIXELS_PER_CM + yOffset,
				                pocket.getWidth() * PIXELS_PER_CM,
				                pocket.getHeight() * PIXELS_PER_CM);
				g2.fill(pocket);
				pocket.setFrame(bounds);
			}
			Polygon[] cushions;
			cushions = Table.createCushion();
			g2.setColor(new Color(0,100,0));

			for (Polygon polygon : cushions) {
				int[] xpoints = polygon.xpoints;
				int[] ypoints = polygon.ypoints;
				for (int i = 0; i < polygon.npoints; i++) {
					xpoints[i] = xpoints[i] * (int)PIXELS_PER_CM + xOffset;
					ypoints[i] = ypoints[i] * (int)PIXELS_PER_CM + yOffset;
				}
				g2.fill(polygon);
				for (int i = 0; i < polygon.npoints; i++) {
					xpoints[i] = (xpoints[i] - xOffset) / (int)PIXELS_PER_CM;
					ypoints[i] = (ypoints[i] - yOffset) / (int)PIXELS_PER_CM;
				}
			}

			Line2D.Double baulkLine = Table.createBaulkLine();
			g2.setColor(Color.WHITE);
			baulkLine.x1 = baulkLine.x1 * (int)PIXELS_PER_CM + xOffset;
			baulkLine.y1 =  baulkLine.y1 * (int)PIXELS_PER_CM + yOffset;
			baulkLine.x2 = baulkLine.x2 * (int)PIXELS_PER_CM + xOffset;
			baulkLine.y2 =  baulkLine.y2 * (int)PIXELS_PER_CM + yOffset;
			g2.draw(baulkLine);
			baulkLine.x1 = (baulkLine.x1 - xOffset) / (int)PIXELS_PER_CM;
			baulkLine.y1 = (baulkLine.y1 - yOffset)/ (int)PIXELS_PER_CM;
			baulkLine.x2 = (baulkLine.x2 - xOffset)/ (int)PIXELS_PER_CM;
			baulkLine.y2 = (baulkLine.y2 - yOffset)/ (int)PIXELS_PER_CM;

			Ball[] ballsToDraw = t.getBalls();
			for (int i = 0; i < ballsToDraw.length; i++) {
				Vector position = ballsToDraw[i].getPosition();
				double[] centreCoords = position.getElements();
				double xLeft = centreCoords[0] - ballsToDraw[i].getRadius();
				xLeft = xLeft * PIXELS_PER_CM + xOffset;
				double yTop = centreCoords[1] - ballsToDraw[i].getRadius();
				yTop = yTop * PIXELS_PER_CM + yOffset;
				double diameter = 2* ballsToDraw[i].getRadius() * PIXELS_PER_CM;
				Ellipse2D.Double ball = new Ellipse2D.Double(
				                             xLeft, yTop, diameter, diameter);
				g2.setColor(ballsToDraw[i].getColor());
				g2.fill(ball);
			}

			if (cue != null && !cue.mouseAtSpin) {
				paintCue(g2);
			}
		}
	}

	public void setCue(Cue newCue)
	{
		if (table != null) {
			table.setCue(newCue);
			frame.repaint();
		}
	}

	public JMenuBar makeJMenu()
	{
		JMenuBar ret = new JMenuBar();

		JMenu gameJMenu = new JMenu("Game");
		ret.add(gameJMenu);
		JMenuItem newGame = new JMenuItem("New game");
		newGame.addActionListener(new MenuHandler(MenuHandler.M_NEW_GAME));
		gameJMenu.add(newGame);
		JMenuItem exit = new JMenuItem("Exit");
		exit.addActionListener(new MenuHandler(MenuHandler.M_EXIT));
		gameJMenu.add(exit);

		JMenu playerJMenu = new JMenu("Players");
		ret.add(playerJMenu);
		JMenu playerA = new JMenu("Player A");
		playerJMenu.add(playerA);
		JRadioButtonMenuItem monteCarloAI = new JRadioButtonMenuItem("Monte carlo AI");
		monteCarloAI.addActionListener(new MenuHandler(MenuHandler.P_A_MONTECARLO));
		playerA.add(monteCarloAI);
		JRadioButtonMenuItem monteCarloAI2 = new JRadioButtonMenuItem("Monte carlo AI 2");
		monteCarloAI2.addActionListener(new MenuHandler(MenuHandler.P_A_MONTECARLO2));
		playerA.add(monteCarloAI2);
		JRadioButtonMenuItem tacticalAI = new JRadioButtonMenuItem("Tactical AI");
		tacticalAI.addActionListener(new MenuHandler(MenuHandler.P_A_TACTICAL));
		playerA.add(tacticalAI);
		JRadioButtonMenuItem humanPlayer = new JRadioButtonMenuItem("Human");
		humanPlayer.addActionListener(new MenuHandler(MenuHandler.P_A_HUMAN));
		playerA.add(humanPlayer);
		ButtonGroup playerButtons = new ButtonGroup();
		playerButtons.add(monteCarloAI);
		playerButtons.add(monteCarloAI2);
		playerButtons.add(tacticalAI);
		playerButtons.add(humanPlayer);
		if (Game.getPlayer(0) instanceof TacticalAI)
			tacticalAI.setSelected(true);
		else if (Game.getPlayer(0) instanceof MonteCarloAI)
			monteCarloAI.setSelected(true);
		else if (Game.getPlayer(0) instanceof HumanPlayer)
			humanPlayer.setSelected(true);
		else
			playerA.setEnabled(false);

		JMenu playerB = new JMenu("Player B");
		playerJMenu.add(playerB);
		monteCarloAI = new JRadioButtonMenuItem("Monte carlo AI");
		monteCarloAI.addActionListener(new MenuHandler(MenuHandler.P_B_MONTECARLO));
		playerB.add(monteCarloAI);
		monteCarloAI2 = new JRadioButtonMenuItem("Monte carlo AI 2");
		monteCarloAI2.addActionListener(new MenuHandler(MenuHandler.P_B_MONTECARLO2));
		playerB.add(monteCarloAI2);
		tacticalAI = new JRadioButtonMenuItem("Tactical AI");
		tacticalAI.addActionListener(new MenuHandler(MenuHandler.P_B_TACTICAL));
		playerB.add(tacticalAI);
		humanPlayer = new JRadioButtonMenuItem("Human");
		humanPlayer.addActionListener(new MenuHandler(MenuHandler.P_B_HUMAN));
		playerB.add(humanPlayer);
		playerButtons = new ButtonGroup();
		playerButtons.add(monteCarloAI);
		playerButtons.add(monteCarloAI2);
		playerButtons.add(tacticalAI);
		playerButtons.add(humanPlayer);
		if (Game.getPlayer(1) instanceof TacticalAI)
			tacticalAI.setSelected(true);
		else if (Game.getPlayer(1) instanceof MonteCarloAI)
			monteCarloAI.setSelected(true);
		else if (Game.getPlayer(1) instanceof HumanPlayer)
			humanPlayer.setSelected(true);
		else
			playerA.setEnabled(false);

		JMenu noiseJMenu = new JMenu("Noise");
		ret.add(noiseJMenu);
		
		playerA = new JMenu("Player A");
		noiseJMenu.add(playerA);
		JRadioButtonMenuItem offItem = new JRadioButtonMenuItem("Off");
		offItem.addItemListener(new MenuHandler(MenuHandler.N_A_OFF));
		playerA.add(offItem);
		JRadioButtonMenuItem onItem = new JRadioButtonMenuItem("On");
		offItem.addItemListener(new MenuHandler(MenuHandler.N_A_ON));
		playerA.add(onItem);
		ButtonGroup noiseButtons = new ButtonGroup();
		noiseButtons.add(onItem);
		noiseButtons.add(offItem);
		onItem.setSelected(Game.playerHasNoise(0));
		playerB = new JMenu("Player B");
		noiseJMenu.add(playerB);
		offItem = new JRadioButtonMenuItem("Off");
		offItem.addItemListener(new MenuHandler(MenuHandler.N_B_OFF));
		playerB.add(offItem);
		onItem = new JRadioButtonMenuItem("On");
		offItem.addItemListener(new MenuHandler(MenuHandler.N_B_ON));
		playerB.add(onItem);
		noiseButtons = new ButtonGroup();
		noiseButtons.add(onItem);
		noiseButtons.add(offItem);
		onItem.setSelected(Game.playerHasNoise(1));
		JMenuItem configureItem = new JMenuItem("Configure");
		noiseJMenu.add(configureItem);
		configureItem.addActionListener(new MenuHandler(MenuHandler.N_CONF));
		
		return ret;
	}

	public Display(Table t)
	{
		this(t, true);
	}

	public Display(Table t, boolean includeMenu)
	{
		frame = new JFrame();
		table = new SnookerTable(t);
		this.t = t;
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.setExtendedState(JFrame.MAXIMIZED_BOTH);
		frame.getContentPane().setLayout(new BorderLayout());
		scoreFields = new Label[3];
		scoreFields[0] = new Label("Score player A: 0");
		scoreFields[0].setBounds(740 + xOffset, 150 + yOffset, 200, 20);
		scoreFields[1] = new Label("Score player B: 0");
		scoreFields[1].setBounds(740 + xOffset, 170 + yOffset, 200, 20);
		scoreFields[2] = new Label("Active player: A");
		scoreFields[2].setBounds(740 + xOffset, 190 + yOffset, 200, 20);
		frame.add(scoreFields[0]);
		frame.add(scoreFields[1]);
		frame.add(scoreFields[2]);
		frame.add(table);
		if (includeMenu)
			frame.setJMenuBar(makeJMenu());
	}

	public JFrame getFrame()
	{
		return frame;
	}

	public Table getTable()
	{
		return t;
	}

	public void setTable(Table newTable)
	{
		frame.remove(table);
		t = newTable;
		table = new SnookerTable(t);
		frame.add(table);
		drawTable();
	}

	/** Draw the table, deleting the old drawing if it exists
	 * @param t the table to draw
	 */
	public void drawTable()
	{
		frame.setVisible(true);
		frame.repaint();
		scoreFields[0].setText("Score player A: " + t.getGameLogic().getPlayerPoints(true));
		scoreFields[1].setText("Score player B: " + t.getGameLogic().getPlayerPoints(false));
		scoreFields[2].setText("Active player: " + t.getGameLogic().getActivePlayer().getName());
	}
        
        public void addMouseListener(MouseListener l)
        {
            table.addMouseListener(l);
        }

	public void removeMouseListener(MouseListener l)
	{
		table.removeMouseListener(l);
	}
}

